package com.andova.network

import android.app.Dialog
import android.content.Context
import android.view.View
import com.andova.NETWORK
import com.andova.component.AndovaComponentManager
import com.andova.component.net.AndovaNetCallback
import com.andova.component.net.ResponseModel
import com.andova.component.net.converter.gson.GsonConverterFactory
import com.andova.net.NetworkHandleFunc
import com.andova.net.NetworkHandleFunc2
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.work.model.BaseResponseModel
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.Buffer
import retrofit2.Call
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.http.POST
import retrofit2.http.Url
import java.io.IOException
import java.io.InputStream
import java.lang.ref.WeakReference
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.nio.charset.Charset
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.*
import java.util.concurrent.TimeUnit
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

class NetworkApi {
    companion object {
        private var client: NetworkClient? = null
        fun client(timeout: Long = 5L): NetworkClient? {
            if (client != null) return client
            client = Retrofit.Builder().baseUrl("https://localhost/")
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(sslv(timeout) ?: return null).build().create(NetworkClient::class.java)
            return client
        }

        private val hosver = HostnameVerifier { s, sslSession -> true }
        private val x509Trust = object : X509TrustManager {
            override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
            override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
            override fun getAcceptedIssuers(): Array<X509Certificate?> = arrayOfNulls(0)
        }

        private fun sslv(timeout: Long): OkHttpClient? {
            val sc: SSLContext = try {
                SSLContext.getInstance("SSL")
            } catch (e: NoSuchAlgorithmException) {
                return null
            }
            try {
                sc.init(null, arrayOf<TrustManager>(x509Trust), SecureRandom())
            } catch (e: KeyManagementException) {
            } catch (e: NullPointerException) {
            }
            return OkHttpClient.Builder().connectTimeout(timeout, TimeUnit.SECONDS)
                    .readTimeout(timeout, TimeUnit.SECONDS)
                    .writeTimeout(timeout, TimeUnit.SECONDS)
                    .hostnameVerifier(hosver)
                    .sslSocketFactory(sc.socketFactory, x509Trust)
                    .build()
        }
    }
}

interface NetworkClient {
    @POST
    fun obtainSystemTime(@Url url: String): Call<AppSystemTimeVO>
}

class NetworkManager {
    private var refview: WeakReference<View>? = null
    private val netmap: MutableMap<String, NetCalBean<*>> = WeakHashMap(3)

    /**
     * 如果需要递归之类的 又有回调 注意key字段的更新 否则会出现意料之外的问题
     */
    private fun <T : BaseResponseModel<*>> netCallback(key: String, view: View?, reset: Boolean, func: NetworkHandleFunc<T>?, modelClass: Class<T>): NetCallback<out BaseResponseModel<*>>? {
        return netCallback(editLog = true, toast = false, key = key, view = view, reset = reset, func = func, modelClass = modelClass)
    }

    /**
     * 有ui元素回调的 最好使用[newNetCallback]
     */
    private fun <T : BaseResponseModel<*>> netCallback(
            editLog: Boolean, toast: Boolean,
            key: String, view: View?, reset: Boolean,
            func: NetworkHandleFunc<T>?, modelClass: Class<T>
    ): NetCallback<out BaseResponseModel<*>>? {
        view?.run {
            refview?.clear()
            refview = WeakReference(view)
        } ?: run { refview = null }
        if (reset) netmap[key]?.reset()
        if (netmap.size > 3) netmap.remove(netmap.keys.last())
        netmap[key]?.run { return this }
        netmap[key] = NetCalBean(editLog, toast, modelClass, func)
        return netmap[key]
    }

    private fun <T : BaseResponseModel<*>> newNetCallback(
            func: NetworkHandleFunc<T>?, modelClass: Class<T>,
            editLog: Boolean = true, toast: Boolean = false
    ): NetCallback<out BaseResponseModel<*>>? = NetCalBean(editLog, toast, modelClass, func)

    private fun <T : BaseResponseModel<*>> realNetCallback(
            token: String?, callback: (String?, NetworkHandleFunc<T>) -> Unit,
            func: NetworkHandleFunc<T>? = null, retry: (String) -> Boolean = { true }
    ) {
        callback(token, object : NetworkHandleFunc2<T> {
            override fun handle(model: T) {
                when (model.errCode) {
                    0 -> {
                        func?.handle(model)
                        retry(token ?: return)
                    }
                    else -> {
                        // todo
                    }
                }
            }

            override fun complete(@NETWORK code: Int) {
                (func as? NetworkHandleFunc2)?.complete(code)
            }
        })
    }

    fun systemTime(ip: String?, func: NetworkHandleFunc<AppSystemTimeVO>? = null) {
        val url = "http://testwork.yzfuture.com.cn:9989/face/getsystemtime"
        netCallback("systemTime${if (ip.isNullOrEmpty()) null else System.currentTimeMillis()}", null, true, object : NetworkHandleFunc2<AppSystemTimeVO> {
            override fun handle(it: AppSystemTimeVO?) {
                func?.handle(it)
            }

            override fun complete(code: Int) {
                (func as? NetworkHandleFunc2)?.complete(code)
            }
        }, AppSystemTimeVO::class.java)?.call(NetworkApi.client()?.obtainSystemTime(url) ?: return)
    }
}

abstract class NetCallback<T : ResponseModel> : AndovaNetCallback<T> {
    private var page = 1
    private var weakRefCxt: WeakReference<Context>? = null
    fun call(call: Call<out ResponseModel>) = AndovaComponentManager.getInstance().netComponent().netCall().request(this, call)
    fun call(context: WeakReference<Context>, call: Call<T>) {
        weakRefCxt = context
        AndovaComponentManager.getInstance().netComponent().netCall().request(this, call)
    }

    private fun stream(stream: InputStream): String {
        val buffer = ByteArray(128)
        var len: Int
        val sb = StringBuilder()
        try {
            while (stream.read(buffer).also { len = it } != -1) sb.append(String(buffer, 0, len))
            stream.close()
        } catch (e: IOException) {
        }
        return sb.toString()
    }

    private fun convertErrorBody(response: Response<*>): T? = response.body() as? T
    private fun <T> convertErrorBody(response: Response<*>, obj: Class<T>): T? {
        var `is`: InputStream? = null
        try {
            if (response.code() < 400 || response.code() > 500) return null
            `is` = response.errorBody()?.byteStream() ?: return null
            val data = stream(`is`)
            return Gson().fromJson<T>(data, obj)
        } catch (e: Exception) {
        } finally {
            try {
                `is`?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
        return null
    }

    override fun prepare() {
        if (!showDialog() || page != 1) return
        dialog()?.show()
    }

    override fun error(obj: Any) {
        if (obj !is Response<*>) return
        try {
            val body = convertErrorBody(obj)
            log("$obj, error message -> ${body?.message ?: "unknown"}", true)
            handle(body ?: return)
        } catch (e: ClassCastException) {
            printResponse(obj.raw())
        } catch (e: NullPointerException) {
            printResponse(obj.raw())
        }
    }

    override fun success(respBody: T) {
        ++page
        handle(respBody)
    }

    override fun response(vararg objects: Any) {
        try {
            if (dialog() != null) dialog()?.dismiss()
        } catch (e: IllegalArgumentException) {
            e.printStackTrace()
        }
        for (obj in objects) {
            if (obj !is Response<*>) continue
            printRequest(obj.raw().request())
        }
    }

    override fun failure(throwable: Throwable, vararg objects: Any) {
        try {
            if (dialog() != null) dialog()?.dismiss()
        } catch (e: IllegalArgumentException) {
            e.printStackTrace()
        }
        var req: String? = null
        for (obj in objects) {
            if (obj !is Call<*>) continue
            req = getRequest(obj.request())
        }
        when (throwable) {
            is JsonSyntaxException -> log("$req, error message -> 数据解析出错", true)
            is SocketTimeoutException -> log("$req, error message -> 连接超时", true)
            is ConnectException -> log("$req, error message -> 连接服务器异常", true)
            else -> log("$req, unknown throwable -> $throwable", true)
        }
    }

    private fun getRequest(request: Request): String? {
        try {
            val buffer = Buffer()
            request.body()?.writeTo(buffer)
            val charset = Charset.forName("UTF-8")
//            val contentType = request.body()?.contentType()
//            contentType?.run { charset = charset(Charset.forName("UTF-8")) }
            val body = buffer.readString(charset)
            return if (body.isEmpty()) request.toString() else "$request\nthe request body is:$body"
        } catch (e: IOException) {
            log("the request body is:$request", true)
        } catch (e: NullPointerException) {
            log("the request body is:$request", true)
        }
        return null
    }

    private fun printRequest(request: Request) {
        if (!editLog()) return
        log(getRequest(request) ?: return)
    }

    private fun printResponse(response: okhttp3.Response) {
        if (!editLog()) return
        try {
            log(response.body()?.string() ?: return, true)
        } catch (e: NullPointerException) {
            log(e, true)
        } catch (e: IOException) {
            log(e, true)
        }
    }

    fun page(): Int = page
    fun reset() = run { page = 1 }
    open fun toast(): Boolean = false
    open fun dialog(): Dialog? = null
    open fun editLog(): Boolean = true
    open fun showDialog(): Boolean = false
    protected abstract fun handle(respBody: T)
    protected abstract fun model(): Class<T>
    private fun log(log: Any, error: Boolean = false) {
        if (!editLog()) return
        when (log) {
            is String -> if (error) System.err.println(log) else println(log)
            is Throwable -> if (error) System.err.println(log.message ?: "throwable") else println(log.message ?: "throwable")
            is Exception -> if (error) System.err.println(log.message ?: "exception") else println(log.message ?: "exception")
        }
    }
}

class NetCalBean<T : BaseResponseModel<*>>(
        private val editLog: Boolean, private val toast: Boolean,
        private val modelClass: Class<T>, private val func: NetworkHandleFunc<T>?
) : NetCallback<T>() {
    override fun model(): Class<T> = modelClass
    override fun editLog(): Boolean = editLog
    override fun toast(): Boolean = toast
    override fun complete(code: Int): Unit = run { (func as? NetworkHandleFunc2)?.complete(code) }
    override fun handle(respBody: T): Unit = run { func?.handle(respBody) }
}